#ifndef LUAMANAGER_HPP
#define LUAMANAGER_HPP

#include <fstream>
#include <iostream>
extern "C" {
	#include <lua5.1/lua.h>
	#include <lua5.1/lauxlib.h>
	#include <lua5.1/lualib.h>
}
#include <luabind/luabind.hpp>
#include <luabind/object.hpp>
#include <luabind/adopt_policy.hpp>
#include <string>
#include <vector>
#include <unordered_map>

class LuaScript {
	friend class LuaManager;
public:
  LuaScript();
  ~LuaScript();
  bool openFile(std::string _filename);
  bool closeFile();
  std::string getFileName() {return filename;}
  std::vector< std::string > getLuaTables() {return luaTables;}
  lua_State* getLuaState() {return lua_stack;}
  bool isFileOpen(std::string _filename);
  void openTable(const std::string& table_name);
  void openTable(int table_name);
  std::string openTablespace();
  void closeTable();
  void closeAllTables();
  luabind::object ReadFunctionPointer(const std::string& key);
  luabind::object ReadFunctionPointer(int key);
  template <class T> void readDataVector(const std::string& key, std::vector<T>& vector);
  template <class T> void readDataVector(int key, std::vector<T>& vector);
  template <class T> T readData(const std::string& key, T default_value);
  template <class T> T readData(int key, T default_value);
  int readInt(const std::string& key) { return readData<int>(key, 0); }
  int readInt(int key) { return readData<int>(key, 0); }
  std::string readString(const std::string& key) {return readData<std::string>(key,"");}
  std::string readString(int key) {return readData<std::string>(key,"");}
  bool doesFunctionExist(int key) {return doesDataExist(key, LUA_TFUNCTION);}
  bool doesFunctionExist(std::string key) {return doesDataExist(key, LUA_TFUNCTION);}
  bool doesTableExist(int key) {return doesDataExist(key, LUA_TTABLE);}
  bool doesTableExist(std::string key) {return doesDataExist(key, LUA_TTABLE);}
  bool doesDataExist(int key, int type);
  bool doesDataExist(std::string key, int type);
private:
  std::string filename;
  std::vector< std::string > openFiles;
  std::vector< std::string > luaTables;
  lua_State* lua_stack;
}; // class LuaScript

// We only need one Lua State so maybe we should set this up as a singleton
class LuaManager {
  friend class LuaScript;
public:
  ~LuaManager();
  static LuaManager* Instance();
  static void KillInstance();
  lua_State *getLuaState() {return lua_state;}
  void addOpenFile(LuaScript *script);
  void removeOpenFile(LuaScript *script);
  bool execLuaFunction(const std::string& filename, const std::string& function_name, bool open_tablespace = true);
private:
  LuaManager();
  static LuaManager* m_pInstance;
  lua_State* lua_state;
  // List of open files
  std::unordered_map< std::string, LuaScript* > openFiles;
  std::unordered_map< std::string, lua_State* > openThreads;
}; 
// LuaManager singleton

template <class T> void LuaScript::readDataVector(const std::string& key, std::vector<T>& vector) {
  if (luaTables.size() == 0) {
    std::cerr << "Error: there are no tables open\n";
	return;
  }
  openTable(key);
  luabind::object o(luabind::from_stack(lua_stack, -1));
  if (luabind::type(o) != LUA_TTABLE) {
    std::cerr << "Failed to load data from top of the stack\n";
	return;
  }
  for (luabind::iterator it(o), end; it != end; it++) {
    vector.push_back(luabind::object_cast<T>((*it)));
  }
  closeTable();
}

template <class T> void LuaScript::readDataVector(int key, std::vector<T>& vector) {
	if (luaTables.size() == 0) {
		std::cerr << "Error: there are no open tables.\n";
		return;
	}
	openTable(key);
	luabind::object o(luabind::from_stack(lua_stack, -1));
	if (luabind::type(o) != LUA_TTABLE) {
		std::cerr << "Failed to load data from top of the stack\n";
		return;
	}
	for (luabind::iterator it(o), end; it != end; it++) {
		vector.push_back(luabind::object_cast<T>((*it)));
	}
	closeTable();
}

template <class T> T LuaScript::readData(const std::string& key, T default_value) {
	if (luaTables.size() == 0) {
		// Variable is in the global space
		lua_getglobal(lua_stack, key.c_str());
		luabind::object o(luabind::from_stack(lua_stack, -1));
		if (!o) {
			std::cerr << "Error loading data\n";
			std::cerr << "Key: " << key << "\n";
			std::cerr << "Type: " << luabind::type(o) << "\n";
			return default_value;
		}
		try {
			T out = luabind::object_cast<T>(o);
			lua_pop(lua_stack, -1);
			return out;
		} catch (...) {
			std::cerr << "Unable to cast output variable.\n";
			std::cerr << "Key: " << key << "\n";
			return default_value;
		}
	} else {
		luabind::object o(luabind::from_stack(lua_stack, -1));
		if (luabind::type(o) != LUA_TTABLE) {
			std::cerr << "Error: top of the stack is not a table\n";
			std::cerr << "Key: " << key << "\n";
			std::cerr << "Type: " << luabind::type(o) << "\n";
			return default_value;
		}
		try {
			return luabind::object_cast<T>(o[key]);
		} catch (...) {
			std::cerr << "Unable to cast output variable.\n";
			std::cerr << "Key: " << key << "\n";
			return default_value;
		}
	}
}

template <class T> T LuaScript::readData(int key, T default_value) {
	if (luaTables.size() == 0) {
		std::cerr << "Error: there are currently no open tables\n";
		std::cerr << "Key: " << key << "\n";
		return default_value;
	}
	luabind::object o(luabind::from_stack(lua_stack, -1));
	if (luabind::type(o) != LUA_TTABLE) {
		std::cerr << "Error: top of the stack is not a table\n";
		std::cerr << "Key: " << key << "\n";
		std::cerr << "Type: " << luabind::type(o) << "\n";
		return default_value;
	}

	try {
		return luabind::object_cast<T> (o[key]);
	} catch (...) {
		std::cerr << "Unable to cast output variable.\n";
		std::cerr << "Key: " << key << "\n";
		return default_value;
	}
}

#endif
